
package com.example.quicklaunch;

import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.AsyncTask;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.text.TextUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class AllAppListService extends Service {
    private static final String INIT_SERVICE = "com.example.quicklaunch.INIT_SERVICE";
    private static final String APP_REMOVED = "com.example.quicklaunch.APP_REMOVED";
    private static final String APP_ADDED = "com.example.quicklaunch.APP_ADDED";
    private static final String EXTRA_APP_PACKAGE = "packages";
    private static final String EXTRA_INIT_FORCE_LOAD = "force_load";
    private ServiceBinder mBinder;
    private RemoteCallbackList<IServiceCallBack> mCallbacks;
    private static Map<String, String> sPinyinSaved = new HashMap<String, String>();
    private Map<String, AppItem> mItems = new HashMap<String, AppItem>();
    private DBOperator mDbOperator;
    private boolean inited = false;

    @Override
    public void onCreate() {
        super.onCreate();
        MLog.d("onCreate()");
        mBinder = new ServiceBinder();
        mCallbacks = new RemoteCallbackList<IServiceCallBack>();
        startForeground(1, new Notification());
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return true;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent == null ? null : intent.getAction();
        MLog.d("onStartCommand() action=" + action);
        try {
            if (INIT_SERVICE.equals(action)) {
                boolean force = intent.getBooleanExtra(EXTRA_INIT_FORCE_LOAD, false);
                mBinder.init(force);
            } else if (APP_ADDED.equals(action)) {
                String pkg = intent.getStringExtra(EXTRA_APP_PACKAGE);
                if (TextUtils.isEmpty(pkg)) {
                    MLog.e("pak can not be null");
                } else {
                    packageAdded(pkg);
                }
            } else if (APP_REMOVED.equals(action)) {
                String pkg = intent.getStringExtra(EXTRA_APP_PACKAGE);
                if (TextUtils.isEmpty(pkg)) {
                    MLog.e("pak can not be null");
                } else {
                    packageRemoved(pkg);
                }
            }
        } catch (RemoteException e) {
            e.printStackTrace();
            MLog.e("remote exception");
        }
        return Service.START_STICKY;
    }

    private void onDataChanged() {
        final int N = mCallbacks.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                mCallbacks.getBroadcastItem(i).onDataChanged();
            } catch (RemoteException e) {
                // The RemoteCallbackList will take care of removing
                // the dead object for us.
            }
        }
        mCallbacks.finishBroadcast();
    }

    private void onLoadFinish() {
        final int N = mCallbacks.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                mCallbacks.getBroadcastItem(i).onLoadFinish();
            } catch (RemoteException e) {
                // The RemoteCallbackList will take care of removing
                // the dead object for us.
            }
        }
        mCallbacks.finishBroadcast();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mCallbacks.kill();
    }

    @Override
    public IBinder onBind(Intent arg0) {
        System.out.println("yadong  ---- onBInd action = " + arg0.getAction());
        return mBinder;
    }

    private class ServiceBinder extends IAllAppListService.Stub {

        @Override
        public List<AppItem> getMatched(String input) throws RemoteException {
            List<AppItem> output = new ArrayList<AppItem>();
            List<String> marched = new ArrayList<String>();
            Map<String, AppItem> temp = new HashMap<String, AppItem>();
            temp.putAll(mItems);
            for (String key : temp.keySet()) {
                List<String> listKey = stringToList(key);
                if (Matcher.nameMatch(listKey, input, marched)) {
                    AppItem item = temp.get(key);
                    List<Integer> matchedIntex = getMatchIndex(marched, listKey);
                    item.setMatchedIndex(matchedIntex);
                    List<Integer> letterOrdigitMatch = Matcher.letterDigitMatch(input, marched);
                    item.setBitMatchIndex(letterOrdigitMatch);
                    output.add(item);
                }
                marched.clear();
            }
            // Sort the output
            Collections.sort(output, new AppComparetor());
            temp = null;
            return output;
        }

        @Override
        public Map<String, AppItem> allItems() throws RemoteException {
            MLog.d("allItems()");
            synchronized (ACCESSIBILITY_SERVICE) {
                return mItems;
            }
        }

        @Override
        public void init(boolean force) throws RemoteException {
            MLog.d("init() force=" + force);
            if (!inited || force) {
                new MyTask().execute(force);
            } else {
                onLoadFinish();
            }
        }

        @Override
        public String getPinyin(String input) throws RemoteException {
            MLog.d("getPinyin() input=" + input);
            String output = sPinyinSaved.get(input);
            if (output == null) {
                output = AppItem.pinyin(input);
            }
            return output;
        }

        @Override
        public void registerCallback(IServiceCallBack cb) throws RemoteException {
            if (mCallbacks != null) {
                mCallbacks.register(cb);
            }
        }

        @Override
        public void unregisterCallback(IServiceCallBack cb) throws RemoteException {
            if (mCallbacks != null) {
                mCallbacks.unregister(cb);
            }
        }
    }

    private static List<String> stringToList(String keys) {
        List<String> output = new ArrayList<String>();
        if (keys != null) {
            String[] ss = keys.split(" ");
            for (String s : ss) {
                if (!TextUtils.isEmpty(s))
                    output.add(s);
            }
        }
        return output;
    }

    private class MyTask extends AsyncTask<Boolean, Void, Void> {
        @Override
        protected Void doInBackground(Boolean... params) {
            mDbOperator = new DBOperator(AllAppListService.this);
            mItems = new HashMap<String, AppItem>();
            sPinyinSaved = mDbOperator.pinyinSaved();
            long start1 = System.currentTimeMillis();
            final PackageManager packageManager = getPackageManager();
            final Intent mainIntent = new Intent(Intent.ACTION_MAIN,
                    null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
            final List<ResolveInfo> apps =
                    packageManager.queryIntentActivities(
                            mainIntent, 0);
            long start2 = System.currentTimeMillis();
            for (ResolveInfo info : apps) {
                AppItem appItem = new AppItem();
                String lable = info.loadLabel(packageManager).toString();
                appItem.setAppLable(lable, sPinyinSaved.get(lable));
                appItem.setAppClassName(info.activityInfo.name);
                appItem.setAppPackage(info.activityInfo.packageName);
                appItem.setResolveInfo(info);
                add(appItem);
                if (params[0]) {
                    mDbOperator.updatePackage(appItem);
                } else {
                    mDbOperator.newPackage(appItem);
                }
            }
            long end = System.currentTimeMillis();
            MLog.d((end - start1) + "|" + (end - start2));
            return null;
        }

        protected void onPostExecute(Void result) {
            MLog.d("onPostExecute");
            onLoadFinish();
            inited = true;
        };
    };

    private void add(AppItem appItem) {
        synchronized (mItems) {
            mItems.put(appItem.getLablePinyin(), appItem);
        }
    }

    public void packageAdded(String pkg) throws RemoteException {
        synchronized (mItems) {
            MLog.d("packageAdded() pkg=" + pkg);
            if (TextUtils.isEmpty(pkg)) {
                MLog.e("pak can not be null");
            }
            PackageManager packageManager = getPackageManager();
            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
            mainIntent.setPackage(pkg);
            final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
            for (ResolveInfo info : apps) {
                AppItem appItem = AppItem.parse(this, info);
                add(appItem);
            }
            onDataChanged();
        }
    }

    public void packageRemoved(String pkg) throws RemoteException {
        MLog.d("packageRemoved() pkg=" + pkg);
        synchronized (mItems) {
            Map<String, AppItem> item = new HashMap<String, AppItem>();
            item.putAll(mItems);
            Set<String> keys = mItems.keySet();
            for (String key : keys) {
                if (pkg.equals(item.get(key).getAppPackage())) {
                    item.remove(key);
                }
            }
            mItems = item;
            onDataChanged();
        }
    }

    /**
     * Use match to compare with app label strings.
     * 
     * @param marched
     * @param app label
     * @return the matched index use to highlight the span text.
     *         {@link com.example.quicklaunch.AppListAdapter.highlightString}
     */
    private List<Integer> getMatchIndex(List<String> marched, List<String> input) {
        List<Integer> matchedIntex = new ArrayList<Integer>();
        for (int index = 0; index < marched.size(); index++) {
            for (int nIndex = 0; nIndex < input.size(); nIndex++) {
                if (marched.get(index).equals(input.get(nIndex))) {
                    matchedIntex.add(nIndex);
                }
            }
        }
        return matchedIntex;
    }

    public static Intent createInitServiceIntent(Context context, boolean force) {
        Intent intent = new Intent(INIT_SERVICE);
        intent.putExtra(EXTRA_INIT_FORCE_LOAD, force);
        return intent;
    }

    public static Intent createAppRemovedIntent(Context context, String pkg) {
        Intent intent = new Intent(APP_REMOVED);
        intent.putExtra(EXTRA_APP_PACKAGE, pkg);
        return intent;
    }

    public static Intent createAppAddedIntent(Context context, String pkg) {
        Intent intent = new Intent(APP_ADDED);
        intent.putExtra(EXTRA_APP_PACKAGE, pkg);
        return intent;
    }

    public static List<String> pinyin(String s) {
        String pinyin = sPinyinSaved.get(s);
        if (pinyin == null) {
            pinyin = AppItem.pinyin(s);
        }
        return stringToList(pinyin);
    }

}
